#!/bin/bash
#
# Copyright (C) 2015 Jonathan Rajotte <jonathan.rajotte-julien@efficios.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#

TEST_DESC="LTTng-crash & shm testing"

CURDIR=$(dirname $0)/
TESTDIR=$CURDIR/../../../
CRASH_BIN="lttng-crash"

# Test app for ust event
TESTAPP_PATH="$TESTDIR/utils/testapp"
TESTAPP_NAME="gen-ust-events"
TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME"
NR_USEC_WAIT=0
NR_ITER=-1

# Temp file output
OUTPUT_DIR=$(mktemp -d -t tmp.test_crash_shm.XXXXXX)

LAST_APP_PID=

NUM_TESTS=76

source $TESTDIR/utils/utils.sh

# Global declaration for simplification
LTTNG_CRASH=$TESTDIR/../src/bin/lttng-crash/$CRASH_BIN

# MUST set TESTDIR before calling those functions
plan_tests $NUM_TESTS

print_test_banner "$TEST_DESC"

bail_out_if_no_babeltrace

function start_test_app()
{
	local tmp_file=$(mktemp -u -t "tmp.${FUNCNAME[0]}.XXXXXX")

	# Start application with a temporary file.
	$TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT --sync-after-first-event $tmp_file &
	ret=$?
	LAST_APP_PID="${!}"
	APPS_PID="${APPS_PID} ${!}"
	ok $ret "Start application to trace"

	# Wait for the application file to appear indicating that at least one
	# tracepoint has been fired.
	while [ ! -f "$tmp_file" ]; do
		sleep 0.5
	done
	diag "Removing test app temporary file $tmp_file"
	rm -rf $tmp_file
}

function stop_test_apps()
{
	diag "Stopping $TESTAPP_NAME"
	for p in ${APPS_PID}; do
		diag "Stopping $p"
		kill ${p} 2>/dev/null
		wait ${p} 2>/dev/null
		diag "Stopped $p"
	done
	APPS_PID=
}

function stop_test_app()
{
	local pid="$1"
	for p in ${pid}; do
		diag "Stopping $p"
		kill ${p} 2>/dev/null
		wait ${p} 2>/dev/null
		diag "Stopped $p"
	done
}

function verify_path_dont_exists()
{
	local path=$1

	while find $path -mindepth 1 -maxdepth 1 &>/dev/null ; do
		sleep 2
	done
	return 0
}

function test_shm_path_per_pid()
{
	diag "Shm: ust per-pid test"
	local session_name=shm_path_per_pid
	local channel_name=channel_per_pid
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm.XXXXXX")

	# Build up
	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-pid"

	start_lttng_tracing_ok $session_name

	diag "Shm: clean state"
	file_count=$(find $shm_path -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -eq "0"
	ok $? "No file created on set-up"

	# Look for per-pid folder structure
	# Start first test app
	diag "Shm: check folder creation and structure"

	start_test_app
	first_app_pid=$LAST_APP_PID
	shm_session_path=$(find $shm_path -mindepth 1 -maxdepth 1)

	file_count=$(echo "$shm_session_path"| wc -l)
	test $file_count -eq "1"
	ok $? "Path $shm_session_path created on application creation"

	first_pid_path=$(find $shm_session_path/ust/pid -mindepth 1 -maxdepth 1)
	ok $? "Pid path exists: $first_pid_path"

	file_count=$(find $shm_session_path/ust/pid -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -eq "1"
	ok $? "Expect 1 pid registration folder got $file_count"

	# Check for buffer and metadata presence in ust/pid/appfolder
	file_count=$(find $first_pid_path/ -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -ne "0"
	ok $? "Expect > 0 buffer and metadata files got $file_count"

	# Start second application pid
	diag "Shm: check basic creation of second ust application"

	start_test_app
	second_app_pid=$LAST_APP_PID

	file_count=$(find $shm_session_path/ust/pid -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -eq "2"
	ok $? "Expect 2 pid registration folders got $file_count"

	# Stop first test application and check for cleanup
	stop_test_app "$first_app_pid"
	APPS_PID="$second_app_pid"
	verify_path_dont_exists "$first_pid_path"
	ok $? "First pid cleanup"

	# Stop all applications and check for full cleanup
	stop_test_apps
	verify_path_dont_exists "$shm_session_path"
	ok $? "Full cleanup"

	# Tear down
	destroy_lttng_session_ok $session_name
	stop_lttng_sessiond
	rm -rf $shm_path
}

function test_shm_path_per_uid()
{
	diag "Shm: ust per-uid test"
	local session_name=shm_path_per_uid
	local channel_name=channel_per_uid
	set -x
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path.XXXXXX")
	set +x

	# Build up
	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-uid"

	start_lttng_tracing_ok $session_name

	diag "Shm: test clean state"
	file_count=$(find $shm_path -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -eq "0"
	ok $? "No files created on set-up"

	# Look for per-pid folder structure
	# Start first test app
	diag "Shm: check folder creation and structure"

	start_test_app
	shm_session_path=$(find $shm_path -mindepth 1 -maxdepth 1)
	file_count=$(echo "$shm_session_path"| wc -l)
	test $file_count -eq "1"
	ok $? "Path $shm_session_path created on application creation"

	uid_path=$(find $shm_session_path/ust/uid -mindepth 1 -maxdepth 1)
	ok $? "uid path exist found $uid_path"

	file_count=$(find $shm_session_path/ust/uid -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -eq "1"
	ok $? "Expect 1 uid registration folder got $file_count"

	# Stop all applications and check for uid presence
	stop_test_apps
	file_count=$(find $shm_session_path/ust/uid -mindepth 1 -maxdepth 1 | wc -l)
	test $file_count -eq "1"
	ok $? "Expect 1 uid registration folder got $file_count"

	# Test full cleanup
	destroy_lttng_session_ok $session_name
	verify_path_dont_exists "$shm_session_path"
	ok $? "Full cleanup"

	stop_lttng_sessiond
	rm -rf $shm_path
}

function test_lttng_crash()
{
	diag "Lttng-crash: basic recuperation"
	local session_name=crash_test
	local channel_name=channel_crash
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path.XXXXXX")
	local shm_path_symlink=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path_symlink.XXXXXX")
	local event_name="tp:tptest"

	# Create a session in snapshot mode to deactivate any use of consumerd
	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path --snapshot"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-uid"
	enable_ust_lttng_event_ok $session_name $event_name $channel_name
	start_lttng_tracing_ok $session_name

	# Generate 10 events
	$TESTAPP_BIN -i 10 -w 0
	stop_lttng_tracing_ok

	crash_recup_count=$($LTTNG_CRASH $shm_path | wc -l)
	test $crash_recup_count -eq "10"
	ok $? "Expect 10 recup event from buffers got $crash_recup_count"

	# Test with symlink
	cp -rs $shm_path/. $shm_path_symlink
	crash_recup_count=$($LTTNG_CRASH $shm_path_symlink | wc -l)
	test $crash_recup_count -eq "10"
	ok $? "Expect 10 recup event from symlink buffers got $crash_recup_count"

	# Tear down
	destroy_lttng_session_ok $session_name
	stop_lttng_sessiond
	rm -rf $shm_path
	rm -rf $shm_path_symlink
}

function test_lttng_crash_extraction()
{
	diag "Lttng-crash: extraction to path"
	local session_name=crash_test
	local channel_name=channel_crash
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path.XXXXXX")
	local extraction_dir_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_extraction_dir_path.XXXXXX")
	local extraction_path=$extraction_dir_path/extract
	local event_name="tp:tptest"

	# Create a session in snapshot mode to deactivate any use of consumerd
	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path --snapshot"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-uid"
	enable_ust_lttng_event_ok $session_name $event_name $channel_name

	start_lttng_tracing_ok $session_name
	# Generate 10 events
	$TESTAPP_BIN -i 10 -w 0
	stop_lttng_tracing_ok

	$LTTNG_CRASH -x $extraction_path $shm_path
	ok $? "Extraction of crashed buffers to path"

	# Test extracted trace
	trace_match_only $event_name 10 $extraction_path

	# Tear down
	destroy_lttng_session_ok $session_name
	stop_lttng_sessiond
	rm -rf $shm_path
	rm -rf $extraction_dir_path
}

function test_shm_path_per_pid_sigint()
{
	diag "Shm: ust per-pid test sigint"
	local session_name=shm_path_per_pid
	local channel_name=channel_per_pid
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path.XXXXXX")
	local num_files=0

	# Build up
	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-pid"

	start_lttng_tracing_ok $session_name

	start_test_app
	start_test_app
	shm_session_path=$(find $shm_path -mindepth 1 -maxdepth 1)

	# Stop sessiond with sigint
	stop_lttng_sessiond SIGINT

	# Looking for a full cleanup
	verify_path_dont_exists "$shm_session_path"
	ok $? "Full cleanup on sigint"

	# Tear down
	stop_test_apps
	rm -rf $shm_path
}

function test_shm_path_per_uid_sigint()
{
	diag "Shm: ust per-uid test sigint"
	local session_name=shm_path_per_uid_sigint
	local channel_name=channel_per_uid_sigint
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path.XXXXXX")
	local ret=0

	# Build up
	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-uid"

	start_lttng_tracing_ok $session_name

	start_test_app
	start_test_app
	shm_session_path=$(find $shm_path -mindepth 1 -maxdepth 1)

	# Test full cleanup on SIGINT
	stop_lttng_sessiond SIGINT

	# Looking for a full cleanup
	verify_path_dont_exists "$shm_session_path"
	ok $? "Full cleanup on sigint"

	# Tear down
	stop_test_apps
	rm -rf $shm_path
}

function test_lttng_crash_extraction_sigkill()
{
	diag "Lttng-crash: extraction with sigkill"
	local session_name=crash_test
	local channel_name=channel_crash
	local shm_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_shm_path.XXXXXX")
	local extraction_dir_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_extraction_dir_path.XXXXXX")
	local extraction_path=$extraction_dir_path/extract
	local event_name="tp:tptest"
	local ret=0

	start_lttng_sessiond
	create_lttng_session_ok $session_name $OUTPUT_DIR "--shm-path $shm_path"
	enable_ust_lttng_channel_ok $session_name $channel_name "--buffers-uid"
	enable_ust_lttng_event_ok $session_name $event_name $channel_name
	start_lttng_tracing_ok $session_name

	# Generate 10 events
	$TESTAPP_BIN -i 10 -w 0

	sigstop_lttng_sessiond
	sigstop_lttng_consumerd

	# Kill the consumers then sessiond with sigkill
	stop_lttng_consumerd SIGKILL
	stop_lttng_sessiond SIGKILL

	$LTTNG_CRASH -x $extraction_path $shm_path
	ret=$?
	ok $ret "Extraction of crashed buffers to path $extraction_path"

	# Test extracted trace
	trace_match_only $event_name 10 $extraction_path

	# Tear down
	stop_test_apps
	rm -rf $shm_path
	rm -rf $extraction_dir_path
}

function interrupt_cleanup()
{
    diag "*** Cleaning-up test ***"
    stop_test_apps
    full_cleanup
}

TESTS=(
	test_shm_path_per_pid
	test_shm_path_per_uid
	test_shm_path_per_pid_sigint
	test_shm_path_per_uid_sigint
	test_lttng_crash
	test_lttng_crash_extraction
	test_lttng_crash_extraction_sigkill
)

trap interrupt_cleanup SIGTERM SIGINT

for fct_test in ${TESTS[@]};
do
	${fct_test}
	if [ $? -ne 0 ]; then
		break;
	fi
done
rm -rf $OUTPUT_DIR

OUTPUT_DEST=/dev/null 2>&1
